﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Reflection;

namespace TestMonkey
{
	public static class ReflectionExtensions
	{

		public static bool IsStatic(this Type type)
		{
			if (type == null)
				throw new ArgumentNullException("type", "type is null.");

			return type.IsAbstract && type.IsSealed;
		}

		public static bool IsNumeric(this Type type)
		{
			if (type == typeof(byte)) return true;
			if (type == typeof(SByte)) return true;
			if (type == typeof(UInt16)) return true;
			if (type == typeof(Int16)) return true;
			if (type == typeof(UInt32)) return true;
			if (type == typeof(Int32)) return true;
			if (type == typeof(UInt64)) return true;
			if (type == typeof(Int64)) return true;
			if (type == typeof(Decimal)) return true;
			if (type == typeof(Single)) return true;
			if (type == typeof(Double)) return true;
			if (type == typeof(BigInteger)) return true;

			return false;
		}


		public static bool IsSignedNumeric(this Type type)
		{
			if (type == typeof(byte)) return false;
			if (type == typeof(UInt16)) return false;
			if (type == typeof(UInt32)) return false;
			if (type == typeof(UInt64)) return false;

			if (type == typeof(SByte)) return true;
			if (type == typeof(Int16)) return true;
			if (type == typeof(Int32)) return true;
			if (type == typeof(Int64)) return true;
			if (type == typeof(Decimal)) return true;
			if (type == typeof(Single)) return true;
			if (type == typeof(Double)) return true;
			if (type == typeof(BigInteger)) return true;

			return false;
		}



		public static bool IsMinMaxNumeric(this Type type)
		{
			if (type == typeof(byte)) return true;
			if (type == typeof(SByte)) return true;
			if (type == typeof(UInt16)) return true;
			if (type == typeof(Int16)) return true;
			if (type == typeof(UInt32)) return true;
			if (type == typeof(Int32)) return true;
			if (type == typeof(UInt64)) return true;
			if (type == typeof(Int64)) return true;
			if (type == typeof(Decimal)) return true;
			if (type == typeof(Single)) return true;
			if (type == typeof(Double)) return true;

			return false;
		}

		public static bool CanActuallyRead(this PropertyInfo property)
		{
			if (property == null)
				throw new ArgumentNullException("property", "property is null.");

			return property.GetGetMethod() != null;
		}

		public static bool CanActuallyWrite(this PropertyInfo property)
		{
			if (property == null)
				throw new ArgumentNullException("property", "property is null.");

			return property.GetSetMethod() != null;
		}

		public static bool HasMethods(this Type type)
		{
			if (type == null)
				throw new ArgumentNullException("type", "type is null.");

			return type.GetMethods().Count() > 0;
		}

		public static bool HasConstructors(this Type type)
		{
			if (type == null)
				throw new ArgumentNullException("type", "type is null.");

			return type.GetConstructors().Count() > 0;
		}

		public static bool IsCreatable(this Type type)
		{
			return IsCreatable(type, Enumerable.Empty<Type>());
		}

		public static bool IsCreatable(this Type type, IEnumerable<Type> blacklist)
		{
			if (type == null)
				throw new ArgumentNullException("type", "type is null.");
			if (blacklist == null)
				throw new ArgumentNullException("blacklist", "blacklist is null.");

			if (type.IsGenericTypeDefinition)
				return false;

			if (type.IsGenericType)
				return false;

			if (type.IsAbstract)
				return false;

			if (type.IsInterface)
				return false; //todo: handle common cases such as IList<T>

			if (type.IsValueType)
				return true; //always creatable

			if (type.GetConstructor(new Type[0]) != null)
				return true; //default constructor

			if (blacklist.Contains(type))
				return false; //prevents circular dependencies

			var newBlackList = blacklist.ToList();
			newBlackList.Add(type);

			foreach (var constructor in type.GetConstructors())
			{
				var isValid = true;
				foreach (var param in constructor.GetParameters())
				{
					isValid = isValid && IsCreatable(param.ParameterType, newBlackList);
				}
				if (isValid)
					return true;
			}

			return false;
		}

		public static bool IsCreatable(this ConstructorInfo constructor)
		{
			if (constructor == null)
				throw new ArgumentNullException("constructor", "constructor is null.");

			return IsCreatable(constructor, Enumerable.Empty<Type>());
		}

		public static bool IsCreatable(this ConstructorInfo constructor, IEnumerable<Type> blacklist)
		{
			if (constructor == null)
				throw new ArgumentNullException("constructor", "constructor is null.");
			if (blacklist == null)
				throw new ArgumentNullException("blacklist", "blacklist is null.");

			Type type = constructor.ReflectedType;

			var newBlackList = blacklist.ToList();
			newBlackList.Add(type);

			var isValid = true;
			foreach (var param in constructor.GetParameters())
			{
				isValid = isValid && IsCreatable(param.ParameterType, newBlackList);
			}

			return isValid;

		}

		public static ConstructorInfo GetDefaultConstructor(this Type type)
		{
			if (type == null)
				throw new ArgumentNullException("type", "type is null.");

			return type.GetConstructor(new Type[0]);
		}

		public static bool HasDefaultConstructor(this Type type)
		{
			if (type == null)
				throw new ArgumentNullException("type", "type is null.");

			return type.GetConstructor(new Type[0]) != null;
		}

		public static string GetConstructorCode(this Type type)
		{
			if (type == null)
				throw new ArgumentNullException("type", "type is null.");
			if (type.IsValueType)
				return "default(" + "global::" + type.FullName + ")";

			if (type == typeof(string))
				return "\"A\"";

			if (type.GetDefaultConstructor() != null)
			{
				return GetConstructorCode(type.GetDefaultConstructor());
			}

			var newBlackList = new List<Type>();
			newBlackList.Add(type);

			foreach (var constructor in type.GetConstructors())
			{
				if (constructor.IsCreatable())
					return GetConstructorCode(constructor);
			}

			throw new ArgumentException("Type " + type.FullName + " is not constructable.");
		}

		public static string GetConstructorCode(this ConstructorInfo constructor)
		{
			if (constructor == null)
				throw new ArgumentNullException("constructor", "constructor is null.");

			Type type = constructor.ReflectedType;

			var parameters = new List<string>();

			foreach (var parameter in constructor.GetParameters())
			{
				parameters.Add(GetConstructorCode(parameter.ParameterType));
			}

			var template = "new " + "global::" + type.FullName + "(" + string.Join(", ", parameters) + ")";
			return template;
		}


	}
}
